#include "src/scene/render.h"

#include "src/scene/scene.h"
#include "src/scene/mask.h"

#include "src/defines/functions.h"

void Render::calculateLine(unsigned char **_line, unsigned int _i, unsigned int _m,
                           unsigned int _nPerPixel, unsigned int _mPerPixel, Mask *_mask, bool _onlyPhotons)
{

    for(unsigned int j = 0; j < _m; j++)//largura
    {
        #pragma omp parallel for
        for(unsigned int k = 0; k < _mPerPixel; k++)
        {
            for(unsigned int l = 0; l < _nPerPixel; l++)
            {
                Color color = calculate(scene->getCamera()->getRay(j, _i, k, l), depth, _onlyPhotons);
                _mask->setColor(k, l, color);
//                    #if DEBUG
//                    if(color != Color(0,0,0,1)) {std::cout <<"[" << k << ", " << l<< "] "; color.print();}
//                    #endif //DEBUG
            }
        }

        Color resultantColor = _mask->getColor();
        (*_line)[j*3] = (unsigned int)floor(resultantColor[0] * 255);//red
        (*_line)[j*3 + 1] = (unsigned int)floor(resultantColor[1] * 255);//green
        (*_line)[j*3 + 2] = (unsigned int)floor(resultantColor[2] * 255);//blue


//            #if DEBUG
//            if(resultantColor != Color(0,0,0,1)) {std::cout <<"resultantColor "; resultantColor.print();
//            std::cout <<"cor que era antes "; raytracing(camera->getRay(j, i, 0, 0), depth).print();}
//            #endif //DEBUG

    }
}

Ray Render::reflectedRay(const Vector3D &_point, const Vector3D &_normal,
                                 const Vector3D &_incident, Object *_goingToObject) const
{
    double cosI = -_normal.dotProduct(_incident);
    Vector3D reflected = _incident + (_normal * (2 * cosI));

    #if DEBUG
        if(!reflected.isNormalized()) std::cout << "vetor reflexao não é normalizado" << std::endl;
    #endif //DEBUG

    return Ray(_point + (reflected*ERROR*10), reflected, _goingToObject);
}

Color Render::findReflectionColor(const Vector3D &_point, const Vector3D &_normal,
                                 const Vector3D &_incident, unsigned int _depth,
                                 Object *_goingToObject, bool _onlyPhotons) const
{
    return calculate(reflectedRay(_point, _normal, _incident, _goingToObject), _depth, _onlyPhotons);
}

Ray Render::transmittedRay(const Vector3D &_point, const Vector3D &_normal,
                                   const Vector3D &_incident, double _n1, double _n2, bool *_isTransmitted,
                                   Object *_goingToObject) const
{
    double n = _n1 / _n2;
    double cosI = -_normal.dotProduct(_incident);//a normal ja tem que estar no sentido correto
    double sinT2 = n * n * (1 - (cosI * cosI));

//    std::cout <<"sinT2 = " << sinT2 << std::endl;
//    int nhe;
//    std::cin >> nhe;

    if (sinT2 <= 1.0)//senão, ele passou do angulo limite e não é refratado
    {
        double cosT = sqrt(1 - sinT2);
        Vector3D transmitted = (_incident * n) + (_normal * ((n*cosI) - cosT)); //a normal ja tem que estar no sentido correto

        #if DEBUG
            if(!transmitted.isNormalized()) std::cout << "vetor refraçao não é normalizado" << std::endl;
        #endif //DEBUG

        *_isTransmitted = true;
        return Ray(_point + (transmitted*ERROR*10), transmitted, _goingToObject);
    }

    *_isTransmitted = false;
    return Ray();
}

Color Render::findTransmittionColor(const Vector3D &_point, const Vector3D &_normal,
                                   const Vector3D &_incident, double _n1, double _n2,
                                   unsigned int _depth, Object *_goingToObject, bool _onlyPhotons) const
{
    bool isTransmitted;

//        std::cout <<"Vai?" << std::endl;

    Ray ray = transmittedRay(_point, _normal, _incident, _n1, _n2, &isTransmitted, _goingToObject);

    if(isTransmitted)
    {
//        std::cout <<"RAIO TRANSMITIDO!" << std::endl;
        return calculate(ray, _depth, _onlyPhotons);
    }

    return Color(-1,-1,-1,-1);
}

double Render::findReflectance(const Vector3D &_normal, const Vector3D &_incident, double _n1, double _n2) const
{
    double n = _n1 / _n2;
    double cosI = -_normal.dotProduct(_incident);//a normal ja tem que estar no sentido correto
    double sinT2 = n * n * (1 - (cosI * cosI));
    if (sinT2 <= 1.0)//senão, ele passou do angulo limite e não é refratado
    {
        double cosT = sqrt(1 - sinT2);
        double rOrth = ((_n1 * cosI) - (_n2 * cosT))/((_n1 * cosI) + (_n2 * cosT));
        double rPar = ((_n2 * cosI) - (_n1 * cosT))/((_n2 * cosI) + (_n1 * cosT));
        return ((rOrth*rOrth) + (rPar * rPar))/2.0;
    }
    return -1;
}

Ray Render::diffusedRay(Object *, const Vector3D &_point, const Vector3D &_normal, const Vector3D &/*_incident*/, Object *_goingToObject) const
{

    //poderia usar um que pegasse alguma informaão do objeto

    double ns = (rand()%101)/100.0;//[0,1]
    double us = (rand()%201)/100.0 - 1;//[-1,1]
    double vs = (rand()%201)/100.0 - 1;//[-1,1]


//    std::cout<< "ns = " << ns << "   us = " << us << "    vs = " << vs << std::endl;

    Vector3D u, v;
    Vector3D test(1, 0, 0);
    if(fabs(fabs(_normal.dotProduct(test)) - 1) < ERROR)
    {
//        std::cout <<"ENTROU dot = " << _normal.dotProduct(test) << " ERROR = " << ERROR << std::endl;
//
//        std::cout <<"normal "; _normal.print();
//        std::cout <<"test "; test.print();
        //se eles forem na mesma direção
        test = Vector3D(0, 1, 0);
    }

    u = _normal.crossProduct(test).normalize();
    v = _normal.crossProduct(u).normalize();

//    std::cout <<"----\nnormal "; _normal.print();
//    std::cout <<"test "; test.print();
//    std::cout <<"Vetor u"; u.print();
//    std::cout <<"Vetor v"; v.print();

    Vector3D diffused = ((_normal*ns) + (u*us) + (v*vs)).normalize();

    //a direção é de acordo com uma esfera
    return Ray(_point + (diffused*ERROR*10), diffused, _goingToObject);
}

Object *Render::findN1N2(double *_n1, double *_n2, Object *_comingFromObject, Object *_goingToObject) const
{
    //se estava vindo do ar, vai ser o objeto colidido
    //se estava vindo de dento de alguem,
        //e bateu nele mesmo, não vai está dentro de ninguem  ( NEM SEMPRE È VERDADE! )
        //e bateu em outro, vai estar dentro do objeto colidido
    if(!_comingFromObject)
    {
        *_n1 = AIR_REFRACTION_INDEX;
        *_n2 = _goingToObject->getMaterial().refractiveIndex;
        return _goingToObject;
    }
    else
    {
        if(_comingFromObject == _goingToObject)
        {
            *_n1 = _goingToObject->getMaterial().refractiveIndex;
            *_n2 = AIR_REFRACTION_INDEX;
            return NULL;
        }
        else
        {
            *_n1 = _comingFromObject->getMaterial().refractiveIndex;
            *_n2 = _goingToObject->getMaterial().refractiveIndex;
            return _goingToObject;
        }
    }
    return NULL;
}
